namespace Sustainalytics.Portfolios.BL
{
    using Newtonsoft.Json;
    using Sustainalytics.DataLayer.Shared;
    using Sustainalytics.Entities;
    using Sustainalytics.Portfolios.BL.Exceptions;
    using Sustainalytics.Portfolios.BL.Validators;
    using Sustainalytics.Replication.Publisher;
    using Sustainalytics.Utils;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    public interface IManualSystemPortfolioUpdatesHandler
    {
        SystemPortfolio Add(string name, Guid[] associatedUniverseIds, PortfolioUserClaims userClaims);

        void DeletePortfolio(Guid id, PortfolioUserClaims userClaims);

        Portfolio SetAssociatedUniverses(Guid id, Guid[] associatedUniverseIds, PortfolioUserClaims userClaims);

        Portfolio UpdatePortfolioName(Guid id, string newName, PortfolioUserClaims userClaims);
    }

    public class ManualSystemPortfolioUpdatesHandler : IManualSystemPortfolioUpdatesHandler
    {
        private readonly IRepositoryCollection<Portfolio> _portfolioCollection;

        private readonly IScreeningToolFilterManager _screeningToolFilterManager;

        private readonly ISubscribeToPortfolioManagement _subscriptionsManagement;

        public ManualSystemPortfolioUpdatesHandler(PortfolioTypeCollection<Portfolio> portfolioCollection, IScreeningToolFilterManager screeningToolFilterManager, ISubscribeToPortfolioManagement subscriptionsManagement)
        {
            this._portfolioCollection = portfolioCollection.SystemCollections;
            this._screeningToolFilterManager = screeningToolFilterManager;
            this._subscriptionsManagement = subscriptionsManagement;
        }

        public SystemPortfolio Add(string name, Guid[] associatedUniverseIds, PortfolioUserClaims userClaims)
        {
            var newPortfolio = SystemPortfolioBuilder.CreateNew(name, userClaims);

            if (!PortfolioValidator.NameValidator(this._portfolioCollection, userClaims).IsValid(newPortfolio, name))
            {
                throw new PortfolioNameAlreadyExists();
            }

            this.SaveAssociatedUniverses(associatedUniverseIds, userClaims, newPortfolio);
            return newPortfolio;
        }

        public void DeletePortfolio(Guid id, PortfolioUserClaims userClaims)
        {
            var portfolio = this._portfolioCollection.Read(id);

            if (!PortfolioValidator.Write().IsValid(portfolio, userClaims))
            {
                throw new PortfolioDeleteUnauthorizedException();
            }

            this._subscriptionsManagement.DeleteSubscriptionToPortfolio(portfolio.Id);

            // update all filters
            this._screeningToolFilterManager.RemovePortfolioFromScreeningToolFilters(portfolio.Id);
        }

        public Portfolio SetAssociatedUniverses(Guid id, Guid[] associatedUniverseIds, PortfolioUserClaims userClaims)
        {
            var portfolio = this._portfolioCollection.Read(id) as SystemPortfolio;
            if (portfolio == null)
            {
                throw new ArgumentNullException("portfolio");
            }

           return this.SaveAssociatedUniverses(associatedUniverseIds, userClaims, portfolio);
        }

        public Portfolio UpdatePortfolioName(Guid id, string newName, PortfolioUserClaims userClaims)
        {
            if (string.IsNullOrEmpty(newName))
            {
                throw new PortfolioNameNullException();
            }

            var portfolio = this._portfolioCollection.Read(id);
            if (!PortfolioValidator.Write().IsValid(portfolio, userClaims))
            {
                throw new PortfolioModificationUnauthorizedException();
            }

            if (!PortfolioValidator.NameValidator(this._portfolioCollection, userClaims).IsValid(portfolio, newName))
            {
                throw new PortfolioNameAlreadyExists();
            }

            portfolio.Name = newName;
            portfolio.DateModified = DateTime.Today;
            this._portfolioCollection.CreateOrUpdate(portfolio);
            return portfolio;
        }

        private Portfolio SaveAssociatedUniverses(IEnumerable<Guid> associatedUniverseIds, PortfolioUserClaims userClaims, SystemPortfolio portfolio)
        {
            if (!PortfolioValidator.Write().IsValid(portfolio, userClaims))
            {
                throw new PortfolioModificationUnauthorizedException();
            }

            var relatedPortfolios = this._portfolioCollection.ReadWhere(x => associatedUniverseIds.Contains(x.Id));

            portfolio.RelatedUniverses =
                relatedPortfolios.Select(x => new SystemPortfolio.RelatedUniverse(x.Id, x.Name)).ToList();

            if (!PortfolioValidator.RelatedUniversesValidator().IsValid(portfolio, relatedPortfolios))
            {
                throw new PortfolioRelatedUniversesMissmatchException();
            }

            portfolio.CompanyCount = relatedPortfolios.Sum(x => x.CompanyCount);
            portfolio.DateModified = DateTime.Today;
            this._portfolioCollection.CreateOrUpdate(portfolio);
            return portfolio;
        }
    }

    public class ReplicationHandler : IManualSystemPortfolioUpdatesHandler
    {
        private readonly Func<IManualSystemPortfolioUpdatesHandler> _func;
        private readonly ServiceBusPublisher _serviceBusPublisher;

        public ReplicationHandler(Func<IManualSystemPortfolioUpdatesHandler> func,ServiceBusPublisher serviceBusPublisher)
        {
            _func = func;
            _serviceBusPublisher = serviceBusPublisher;
        }

        public SystemPortfolio Add(string name, Guid[] associatedUniverseIds, PortfolioUserClaims userClaims)
        {
           
            var portfolio = _func().Add(name, associatedUniverseIds, userClaims);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolio), ReplicationUtils.EntityType.Portfolio, ReplicationUtils.Operation.CreateOrUpdate, ReplicationUtils.ConnectionStringType.ClientDW);
            return portfolio;
        }

        public void DeletePortfolio(Guid id, PortfolioUserClaims userClaims)
        {
            _func().DeletePortfolio(id, userClaims);
        }

        public Portfolio SetAssociatedUniverses(Guid id, Guid[] associatedUniverseIds, PortfolioUserClaims userClaims)
        {
            var portfolio = _func().SetAssociatedUniverses(id, associatedUniverseIds, userClaims);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolio), ReplicationUtils.EntityType.Portfolio, ReplicationUtils.Operation.CreateOrUpdate, ReplicationUtils.ConnectionStringType.ClientDW);
            return portfolio;
        }

        public Portfolio UpdatePortfolioName(Guid id, string newName, PortfolioUserClaims userClaims)
        {
            var portfolio = _func().UpdatePortfolioName(id, newName, userClaims);
            _serviceBusPublisher.SendMessage(JsonConvert.SerializeObject(portfolio), ReplicationUtils.EntityType.Portfolio, ReplicationUtils.Operation.CreateOrUpdate, ReplicationUtils.ConnectionStringType.ClientDW);
            return portfolio;
        }
    }
}